
jsal.module({
    name : 'heist.tests.parser',
    depends : ['heist'],
    onload : function () {
        if (! heist.tests) heist.tests = {};
        heist.tests.parser = new testjs.Test(
            'parser', function () {

		        this.plan(67);

		        this.ok(heist.Parser, 'load the heist parser');

		        var parser = new heist.Parser({ embeddedInCode : false, defaultOutputOn : true });

		        var ast = parser.parseAst(
		            "Hello World!\n"
		        );

		        this.ok(ast.subnodes.length == 1 && ast.subnodes[0], 'parsed single content node');
		        this.isa(ast.subnodes[0], heist.ast.Content, "content node type");
		        this.is(ast.subnodes[0].text, "Hello World!\n", "text in content node");

		        ast = parser.parseAst(
		            "some content\n" +
		            ": a line of code\n" +
		            "more content\n"
		        );

		        this.is(ast.subnodes.length, 3, 'content followed by code line followed by content has three nodes');

		        this.isa(ast.subnodes[0], heist.ast.Content, 'content node type before code line');
		        this.is(ast.subnodes[0].text, "some content\n", 'text in content node before code line (including trailing newline)');

		        this.isa(ast.subnodes[1], heist.ast.CodeLines, 'code lines node correct type');
		        this.is(ast.subnodes[1].code, " a line of code\n", 'got code line including trailing newline');

		        this.isa(ast.subnodes[2], heist.ast.Content, 'content node type after code line');
		        this.is(ast.subnodes[2].text, "more content\n", 'text in content node after code line');


		        ast = parser.parseAst(
		            "...\n" +
		            ": a line of code\n" +
		            ": that runs into another\n" +
		            "...\n"
		        );

		        this.is(ast.subnodes.length, 3, 'content followed by code lines followed by content has three nodes');

		        this.isa(ast.subnodes[0], heist.ast.Content, 'content node type before code lines that run together');

		        this.isa(ast.subnodes[1], heist.ast.CodeLines, 'code lines node correct type when run together');
		        this.is(ast.subnodes[1].code, " a line of code\n that runs into another\n", 'got code lines that run together including trailing newline');

		        this.isa(ast.subnodes[2], heist.ast.Content, 'content node type after code lines that run together');


		        ast = parser.parseAst('');

		        this.is(ast.subnodes.length, 0, 'empty template generates empty ast');


		        ast = parser.parseAst(": just code line");

		        this.is(ast.subnodes.length, 1, 'single code line returns one node');

		        this.isa(ast.subnodes[0], heist.ast.CodeLines, 'got a code single code line');


		        ast = parser.parseAst("<( an expression )>");

		        this.is(ast.subnodes.length, 1, 'single expression tag returns one node');
		        this.isa(ast.subnodes[0], heist.ast.ExpressionTag, 'got single expression tag');
		        this.is(ast.subnodes[0].code, ' an expression ', 'contents of expression tag');


		        ast = parser.parseAst("<(exp1)><(exp2)>");

		        this.is(ast.subnodes.length, 2, 'adjacent expression tags do not run together');
		        this.isa(ast.subnodes[0], heist.ast.ExpressionTag, 'first adjacent expression tag type');
		        this.is(ast.subnodes[0].code, 'exp1', 'first adjacent expression tag code');
		        this.isa(ast.subnodes[1], heist.ast.ExpressionTag, 'second adjacent expression tag type');
		        this.is(ast.subnodes[1].code, 'exp2', 'second adjacent expression tag code');


		        ast = parser.parseAst("<[ any old code ]>");

		        this.is(ast.subnodes.length, 1, 'single code tag returns one node');
		        this.isa(ast.subnodes[0], heist.ast.CodeTag, 'got single code tag');
		        this.is(ast.subnodes[0].code, ' any old code ', 'contents of code tag');


		        ast = parser.parseAst("<[code1]><[code2]>");

		        this.is(ast.subnodes.length, 1, 'adjacent code tags do run together');
		        this.isa(ast.subnodes[0], heist.ast.CodeTag, 'ran together code tag type');
		        this.is(ast.subnodes[0].code, 'code1code2', 'ran together code tag code');


		        error = undefined;
		        try {
		          parser.parseAst(
		            "\n" +
		            "   <( )>\n"
		          );
		        }
		        catch (e) {
		          error = e;
		        }

		        this.ok(error, 'empty expression produces error');
		        this.like(error, /empty expression tag/i, 'error is descriptive of problem');
		        this.like(error, /at line 2, character 4/i, 'line and char info on empty expression error');


		        ast = parser.parseAst(" displayed content |- not displayed content");

		        this.is(ast.subnodes.length, 3, 'template including output stop contains three nodes');

		        this.isa(ast.subnodes[0], heist.ast.Content, 'content before output stop is content node');
		        this.is(ast.subnodes[0].text, ' displayed content ', 'content node before stop has correct content');

		        this.isa(ast.subnodes[1], heist.ast.OutputStop, 'output stop is correct type');

		        this.isa(ast.subnodes[2], heist.ast.IgnoredContent, 'content after output stop is ignored content node');
		        this.is(ast.subnodes[2].text, ' not displayed content', 'content after output stop is retained and correct');


		        error = undefined;
		        try {
		          parser.parseAst("-|");
		        }
		        catch (e) {
		          error = e;
		        }

		        this.ok(error, 'output start where output already on raises error');
		        this.like(error, /output already on/i, 'output start when already on error descriptive');
		        this.like(error, /at line 1, character 1/i, 'output start when already on error has correct position');


		        ast = parser.parseAst("displayed |- not displayed -| displayed");

		        this.is(ast.subnodes.length, 5, 'template including output stop and start contains five nodes');

		        this.isa(ast.subnodes[0], heist.ast.Content, 'content before output stop is content node');
		        this.is(ast.subnodes[0].text, 'displayed ', 'content node before stop has correct content');

		        this.isa(ast.subnodes[1], heist.ast.OutputStop, 'output stop is correct type');


		        this.isa(ast.subnodes[2], heist.ast.IgnoredContent, 'content between output stop and output start is ignored content node');
		        this.is(ast.subnodes[2].text, ' not displayed ', 'content between output stop and output start is retained and correct');

		        this.isa(ast.subnodes[3], heist.ast.OutputStart, 'output start is correct type');

		        this.isa(ast.subnodes[4], heist.ast.Content, 'content after output start is content node');
		        this.is(ast.subnodes[4].text, ' displayed', 'content node after output start has correct content');

		        //}

		        ast = parser.parseAst("<~myMethod>method content</~>");

		        this.is(ast.subnodes.length, 1, 'method has single method node');

		        var methodNode = ast.subnodes[0];
		        this.isa(methodNode, heist.ast.Method, 'method creates method node');
		        this.is(methodNode.argumentList, undefined, 'argument list not defined when not specified');
		        this.is(methodNode.subnodes.length, 1, 'method has one subnode');
		        this.isa(methodNode.subnodes[0], heist.ast.Content, 'content in method is a content node');
		        this.is(methodNode.subnodes[0].text, 'method content', 'content inside method contains right content');


		        error = undefined;
		        try {
		          parser.parseAst(" <~anything>");
		        }
		        catch (e) {
		          error = e;
		        }

		        this.ok(error, 'unclosed method tag results in error');
		        this.like(error, /\<\~anything\.\.\.> tag without corresponding \<\/\~\> closing tag/i, 'unclosed method tag results in descriptive error');
		        this.like(error, /at line 1, character 2/i, 'tag position in unclosed method tag is correct');

		        error = undefined;
		        try {
		          parser.parseAst("<~blah></~blah>");
		        }
		        catch (e) {
		          error = e;
		        }

		        this.nok(error, 'matching close tag okay');

		        error = undefined;
		        try {
		          parser.parseAst(
		            "\n" +
		            "<~foo>\n" + 
		            "</~bar>\n"
		          );
		        }
		        catch (e) {
		          error = e;
		        }

		        this.ok(error, 'mismatch of closing tag causes parse error');
		        this.like(error, /mismatched closing tag \<\/\~bar\> for \<\~foo\.\.\.> tag/, 'mismatched closing tag results in descriptive error');
		        this.like(error, /at line 3, character 1/, 'error reports correct position');
			}
		);
	}
});


